<?php

require_once 'PipesImpl/ModelList.php';
require_once 'PipesImpl/Model/Meta.php';
require_once 'PipesImpl/Visitable.php';
require_once 'Pipes/Exception.php';
require_once 'Pipes/Value/Callback.php';
require_once 'Pipes/Model/Mapper/Factory.php';
require_once 'PipesImpl/Config.php';

abstract class Pipes_ModelList implements PipesImpl_ModelList, PipesImpl_Visitable
{
	private $_records = array();
	private $_modelName = '';
	
	private $_cursor = 0;
	private $_cache  = array();
	private $_saveCallback = null;
	private $_removeCallback = null;

	private $_mapper = null;
	private $_meta = null;
	
	public function __construct(PipesImpl_Config $config, PipesImpl_Model_Meta $meta, array $records) {
		$this->_records = $records;
		reset($this->_records);
		$this->_modelName = substr(get_class($this), 0, -4);
		if (!class_exists($this->_modelName)) {
			require_once str_replace('_', '/', $this->_modelName) . '.php';
		}
		$this->_meta = $meta;

		$name = substr($this->_modelName, strrpos($this->_modelName, '_') + 1);
		$mapperFactory = new Pipes_Model_Mapper_Factory($config);
		$this->_mapper = $mapperFactory->factory($name);
	}
	
	public function current() 
	{
		if (!$this->valid()) {
			throw new Pipes_Exception('Past end of iterator');
		}
		if (!isset($this->_cache[$this->key()])) {
			$modelName = $this->_modelName;
			$this->_cache[$this->key()] = new $modelName($this->_meta, $this->_records[$this->key()]);
			$this->_cache[$this->key()]->attach(array($this->_mapper, 'save'));
		}
		
		return $this->_cache[$this->key()];
	}

	public function key() 
	{
		return $this->_cursor; 
	}
	
	public function next() {
		$this->_cursor++; 
	}
	
	public function rewind() 
	{
		$this->_cursor = 0; 
	}
	
	public function valid() 
	{
		return ($this->_cursor < count($this->_records));
	}
	
	public function item($key) {
		$this->_cursor = $key - 1;
		return $this->current();
	}
	
	public function find($id) {
		$record = current($this->_records);
		reset ($record);
		return $this->_findByColumn(key($record), $id);
	}
	
	/**
	 * @todo refactor into Locator 
	 */
	protected function _findByColumn($column, $findValue) 
	{
		$return = null;
		foreach ($this->_records as $record_cursor => $record) {
			foreach ($record as $key => $value) {
				if ($key == $column) {
					if ($value == $findValue) {
						$this->_cursor = $record_cursor;
						$return = $this->current();
						break;
					}
				}
			}
			if (!is_null($return)) {
				break;
			}
		}
		return $return;
	}
	
	public function __call($method, $arguments) {
		$return = null;

		// todo: refactor into a Factory that returns a Locator and can provide
		// one-off factories to implement custom finding logic
		if (preg_match('/^findBy([A-Za-z0-9_]+)$/', $method, $matches)) {
			$column = $matches[1];
			$return = $this->_findByColumn(strtolower($column), array_shift($arguments));
		}

		return $return;
	}
	
	public function __get($key) 
	{
		$return = null;
		switch ($key) {
			case 'length' :
				$return = count($this->_records);
				break;
			case 'meta' :
				$return = $this->_meta;
				break;
		}
		return $return;
	}

	public function attachSave($callback) {
		$this->_saveCallback = new Pipes_Value_Callback($callback);
	}

	public function attachRemove($callback) 
	{
		$this->_removeCallback = new Pipes_Value_Callback($callback);
	}
	
	public function save() {
		if (is_null($this->_saveCallback)) {
			return;
		}
		
		foreach ($this->_cache as $model) {
			call_user_func($this->_saveCallback->value, $model);
		}
	}
	
	public function add(PipesImpl_Model $model = null) {
		$newKey = count($this->_records);
		$this->_records[] = !is_null($model) ? $model->toArray() : array();
		$this->_cursor = $newKey;
		return $this->current();
	}
	
	public function removeCurrent() {
		$model = $this->current();
		unset($this->_cache[$this->key()]);
		unset($this->_records[$this->key()]);

		if (!is_null($this->_removeCallback)) {
			call_user_func($this->_removeCallback->value, $model);
		}
	}

	public function accept(PipesImpl_Visitor $visitor) {
		$visitor->visit($this);
	}
	
	public function getName() {
		$exploded = explode('_', get_class($this));
		return substr(array_pop($exploded), 0, -4);
	}
}

